home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Utilities / Workspace / Locus / Source / GroupBrowserMatrix.m < prev    next >
Text File  |  1995-06-12  |  17KB  |  668 lines

  1.  
  2. /*
  3.     Copyright 1993  Jeremy Slade.
  4.  
  5.     You are free to use all or any parts of the Locus project
  6.     however you wish, just give credit where credit is due.
  7.     The author (Jeremy Slade) shall not be held responsible
  8.     for any damages that result out of use or misuse of any
  9.     part of this project.
  10.  
  11. */
  12.  
  13. /*
  14.     Project: Locus
  15.     
  16.     Class: GroupBrowserMatrix
  17.     
  18.     Description: See GroupBrowserMatrix.h
  19.     
  20.     Original Author: Jeremy Slade
  21.     
  22.     Revision History:
  23.         Created
  24.             V.101    JGS Wed Feb  3 23:47:02 GMT-0700 1993
  25.  
  26. */
  27.  
  28. #import "GroupBrowserMatrix.h"
  29.  
  30. #import "Dragging.h"
  31. #import "FolderController.h"
  32. #import "Globals.h"
  33. #import "Group.h"
  34. #import "ItemCell.h"
  35. #import "ItemList.h"
  36.  
  37. #import <dpsclient/psops.h>
  38. #import <dpsclient/wraps.h>
  39.  
  40.  
  41. @implementation GroupBrowserMatrix
  42.  
  43.  
  44. // -------------------------------------------------------------------------
  45. //   Stuff used by mouseDown for control-dragging 
  46. // -------------------------------------------------------------------------
  47.  
  48.  
  49. // Cache windows shared by all GroupBrowserMatrices
  50. static id matrixCache, cellCache;
  51.  
  52. #define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK
  53.  
  54. #define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.01);
  55.  
  56. #define stopTimer(timer) if (timer) { \
  57.     NXEndTimer(timer); \
  58.     timer = NULL; \
  59. }
  60.  
  61.  
  62. // -------------------------------------------------------------------------
  63. //   Creating, initializing methods
  64. // -------------------------------------------------------------------------
  65.  
  66.  
  67. + initialize
  68. {
  69.     [self setVersion:GroupBrowserMatrix_VERSION];
  70.     return ( self );
  71. }
  72.  
  73.  
  74.  
  75. // -------------------------------------------------------------------------
  76. //   Loading the Group
  77. // -------------------------------------------------------------------------
  78.  
  79.  
  80. - setGroup:group
  81. /*
  82.     'Loads' the Group (a subclass of List) by replacing our cellList with it.  Also resets selectedCell, etc.   Returns the old cellList.
  83. */
  84. {
  85.     id oldList = cellList;
  86.     NXSize size;
  87.     DrawInfo drawInfo;
  88.         
  89.     cellList = group;
  90.  
  91.     selectedCell = nil;
  92.     selectedRow = -1;
  93.     selectedCol = -1;
  94.     
  95.     numRows = [cellList count];
  96.     numCols = 1;
  97.     
  98.     [group getDrawInfo:&drawInfo]; [ItemCell setDrawInfo:&drawInfo];
  99.     [[group objectAt:0] calcCellSize:&size];
  100.     size.width = bounds.size.width;
  101.     [[self setCellSize:&size] sizeToCells];
  102.     
  103.     return ( oldList );
  104. }
  105.  
  106.  
  107.  
  108. // -------------------------------------------------------------------------
  109. //   Responding to mouseDown events
  110. // -------------------------------------------------------------------------
  111.  
  112.  
  113. - mouseDown:(NXEvent *)theEvent
  114. /*
  115.     On the first click, and if no meta keys are used, it determines which cell was clicked and sends it a startTrackingAt:inView: message.  This makes it possible for the cells to track the mouse even when in NX_LISTMODE.
  116.     If the user control-clicks a cell, it allows them to drag the cell to a new location.
  117. */
  118. {
  119.     NXPoint pt;
  120.     int row, col;
  121.     NXRect cellFrame;
  122.     id clickedCell;
  123.     
  124.     // Allow cells to track mouseDown events
  125.     if ( !theEvent->flags ) {
  126.         pt = theEvent->location;
  127.         [self convertPoint:&pt fromView:nil];
  128.         [self getRow:&row andCol:&col forPoint:&pt];
  129.         clickedCell = [self cellAt:row :0];
  130.         [self selectCell:clickedCell];
  131.         [self getCellFrame:&cellFrame at:row :0];
  132.         if ( [clickedCell mouseDownAt:&pt frame:&cellFrame inView:self] ) {
  133.             [self drawCellInside:clickedCell];
  134.             return ( self );
  135.         }
  136.     }
  137.     
  138.     // Check for control-click
  139.     if ( (theEvent->flags & NX_CONTROLMASK)
  140.             && !(theEvent->flags & NX_SHIFTMASK) ) {
  141.         [self controlDragMouse:theEvent];
  142.         return ( self );
  143.     }
  144.     
  145.     // Check for alternate-click
  146.     if ( (theEvent->flags & NX_ALTERNATEMASK) ) {
  147.         [self alternateDragMouse:theEvent];
  148.         return ( self );
  149.     }
  150.         
  151.     // Just act like normal
  152.     return ( [super mouseDown:theEvent] );
  153. }
  154.  
  155.  
  156.  
  157. - controlDragMouse:(NXEvent *)theEvent
  158. /*
  159.     Called when the user Control-clicks on a cell.  This allows the cells to be reordered by dragging them to a new position.
  160. */
  161. {
  162.     NXPoint pt;
  163.     int row, col, newRow;
  164.     NXRect cellFrame, visibleRect, cellCacheBounds;
  165.     int eventMask;
  166.     float    dy;
  167.     NXEvent    *event = NULL, peek;
  168.     NXTrackingTimer *timer = NULL;
  169.     BOOL scrolled = NO;
  170.     id group;
  171.  
  172.     // Prepare the cell and matrix cache windows
  173.     [self setupCacheWindows];
  174.     
  175.     // Tell the window to receive mouse-dragged events
  176.     eventMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
  177.     
  178.     // Find the cell that got clicked and select it
  179.     pt = theEvent->location;
  180.     [self convertPoint:&pt fromView:nil]; //Convert from window's base coords
  181.     [self getRow:&row andCol:&col forPoint:&pt];
  182.     activeCell = [self cellAt:row :0];
  183.     [self selectCell:activeCell];
  184.     [self getCellFrame:&cellFrame at:row :0];
  185.     
  186.     // Do whatever's required for a single-click
  187.     [self sendAction];
  188.     
  189.     // draw a 'well' in place of the selected cell (see drawSelf::)
  190.     [window disableFlushWindow];
  191.     [self lockFocus];
  192.     [[self drawSelf:&cellFrame :1] unlockFocus];
  193.     [window reenableFlushWindow];
  194.     
  195.     // Copy what's currently visible into the matrix cache
  196.     [[matrixCache contentView] lockFocus];
  197.     [self getVisibleRect:&visibleRect];
  198.     [self convertRect:&visibleRect toView:nil];
  199.     PScomposite ( NX_X(&visibleRect), NX_Y(&visibleRect),
  200.         NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect),
  201.         [window gState], 0.0, NX_HEIGHT(&visibleRect), NX_COPY );
  202.     [[matrixCache contentView] unlockFocus];
  203.  
  204.     // Image the cell into its cache
  205.     [[cellCache contentView] lockFocus];
  206.     [[cellCache contentView] getBounds:&cellCacheBounds];
  207.     [activeCell drawSelf:&cellCacheBounds inView:[cellCache contentView]];
  208.     [[cellCache contentView] unlockFocus];
  209.     
  210.       // save the mouse's location relative to the cell's origin
  211.     dy = pt.y - cellFrame.origin.y;
  212.     
  213.     // All further drawing will be done in self
  214.     [self lockFocus];
  215.     
  216.     event = theEvent;
  217.     while ( event->type != NX_MOUSEUP ) { // Loop until mouse-up event
  218.     
  219.         // Erase the active cell using inmage in matrix cache
  220.         [self getVisibleRect:&visibleRect];
  221.         PScomposite ( NX_X(&cellFrame), NX_HEIGHT(&visibleRect) -
  222.             NX_Y(&cellFrame) + NX_Y(&visibleRect) -
  223.             NX_HEIGHT(&cellFrame), NX_WIDTH(&cellFrame),
  224.             NX_HEIGHT(&cellFrame), [matrixCache gState],
  225.             NX_X(&cellFrame), NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame),
  226.             NX_COPY);
  227.             
  228.         // Move the active cell
  229.         pt = event->location;
  230.         [self convertPoint:&pt fromView:nil];
  231.         cellFrame.origin.y = pt.y - dy;
  232.         
  233.         // Constrain the cell's location to our bounds
  234.         if ( NX_Y(&cellFrame) < NX_X(&bounds) )
  235.             cellFrame.origin.y = NX_X(&bounds);
  236.         else if ( NX_MAXY(&cellFrame) > NX_MAXY(&bounds) )
  237.             cellFrame.origin.y = NX_HEIGHT(&bounds) - NX_HEIGHT(&cellFrame);
  238.  
  239.         // Make sure the cell will be entirely visible in its new location
  240.         if ( !NXContainsRect(&visibleRect,&cellFrame) && mFlags.autoscroll ) {
  241.         
  242.             [window disableFlushWindow];
  243.             [self scrollRectToVisible:&cellFrame];
  244.             [window reenableFlushWindow];
  245.  
  246.             // Copy the new image to the matrix cache
  247.             [[matrixCache contentView] lockFocus];
  248.             [self getVisibleRect:&visibleRect];
  249.             [self convertRect:&visibleRect toView:nil];
  250.             PScomposite ( NX_X(&visibleRect), NX_Y(&visibleRect),
  251.                 NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect),
  252.                 [window gState], 0.0, NX_HEIGHT(&visibleRect), NX_COPY );
  253.             [[matrixCache contentView] unlockFocus];
  254.  
  255.             // Note that we scrolled and start generating timer events
  256.             // for autoscrolling
  257.             scrolled = YES;
  258.             startTimer ( timer );
  259.         } else {
  260.             // No scrolling, so stop any timer
  261.             stopTimer ( timer );
  262.         }
  263.     
  264.         // Composite the active cell's image on top
  265.         PScomposite(0.0, 0.0, NX_WIDTH(&cellFrame), NX_HEIGHT(&cellFrame),
  266.             [cellCache gState], NX_X(&cellFrame),
  267.             NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame), NX_COPY);
  268.         
  269.         // Now flush the display
  270.         [window flushWindow];
  271.     
  272.         // If we autoscrolled, flush any lingering window server events to
  273.         // make the scrolling smooth
  274.         if ( scrolled ) {
  275.             NXPing();
  276.             scrolled = NO;
  277.         }
  278.     
  279.         // Save the location, just in case we need it again
  280.         pt = event->location;
  281.     
  282.         if ( ![NXApp peekNextEvent:MOVE_MASK into:&peek] ) {
  283.             // No mouseMoved, mouseUp event avail, so take mouseMoved,
  284.             // mouseUp, or timer
  285.             event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK];
  286.         } else {
  287.             // get the mouseMoved or mouseUp event in the queue
  288.             event = [NXApp getNextEvent:MOVE_MASK];
  289.         }
  290.     
  291.         // If a timer event, mouse location isn't valid, so set it
  292.         if ( event->type == NX_TIMER )
  293.             event->location = pt;
  294.     }
  295.     
  296.     // mouseUp, so stop any timer and unlock focus
  297.     stopTimer ( timer );
  298.     [self unlockFocus];
  299.     
  300.     // Find the cell under under the mouse's location
  301.     pt = event->location;
  302.     [self convertPoint:&pt fromView:nil];
  303.     if ( ![self getRow:&newRow andCol:&col forPoint:&pt] ) {
  304.         // Mouse is out of bounds, so find the cell the active cell covers
  305.         [self getRow:&newRow andCol:&col forPoint:&cellFrame.origin];
  306.     }
  307.     
  308.     // we need to shuffle cells if the active cell's going to a new location
  309.     if ( newRow != row ) {
  310.         // No autodisplay while we move cells around
  311.         [self setAutodisplay:NO];
  312.  
  313.         // Move the item to it's new location within the group
  314.         group = cellList;
  315.         [group setChanged:YES];
  316.         [group removeObject:activeCell];
  317.         [group insertObject:activeCell at:newRow];
  318.         
  319.         // if the active cell is selected, note its new row
  320.         if ( [activeCell state] )
  321.             selectedRow = newRow;
  322.             
  323.         // make sure the active cell's visible if we're autoscrolling
  324.         if ( mFlags.autoscroll ) {
  325.             [self scrollCellToVisible:newRow :0];
  326.         }
  327.         
  328.         // size to cells after all this shuffling and turn autodisplay back on
  329.         [[self sizeToCells] setAutodisplay:YES];
  330.         
  331.     }
  332.     
  333.     // No longer dragging the cell
  334.     activeCell = nil;
  335.     
  336.     // Now redraw ourself
  337.     [self display];
  338.     
  339.     // set the event mask mask to normal
  340.     [window setEventMask:eventMask];
  341.     
  342.     return ( self );
  343. }
  344.  
  345.  
  346.  
  347. - alternateDragMouse:(NXEvent *)theEvent
  348. /*
  349.     Sent when the user alternate-clicks on a cell.  This allows the selected items to be dragged out of this window to another folder or another application.
  350. */
  351. {
  352.     id clickedCell;
  353.     char *files = NULL, *buf;
  354.     NXPoint pt, offs = { 24,24 };
  355.     int row, col;
  356.     NXStream *stream;
  357.     int len, mlen;
  358.     int i, count, k;
  359.     NXImage *img;
  360.     id    pboard;    
  361.  
  362.     // Determine which cell the click happened in
  363.     pt = theEvent->location;
  364.     [self convertPoint:&pt fromView:nil];
  365.     [self getRow:&row andCol:&col forPoint:&pt];
  366.     clickedCell = [self cellAt:row :0];
  367.     
  368.     // Make sure this cell is selected, then redraw
  369.     [clickedCell setState:1];
  370.     [self drawCellInside:clickedCell];
  371.     
  372.     // behave as a single click
  373.     if ( !(theEvent->flags & NX_SHIFTMASK ) ) [self sendAction];
  374.     
  375.     // Get list of all selected items, and put them into
  376.     // tab-separated string
  377.     stream = NXOpenMemory ( NULL, 0, NX_WRITEONLY );
  378.     if ( !stream ) { // Couldn't open stream
  379.         NXBeep();
  380.         return ( self );
  381.     }
  382.     
  383.     k = 0; // This is the first item in the list
  384.     for ( i=0, count=[cellList count]; i<count; i++ ) {
  385.         if ( [[cellList objectAt:i] state] ) {
  386.             if ( k>0 ) NXPutc ( stream, '\t' );
  387.             NXPrintf ( stream, "%s", [[cellList objectAt:i] path] );
  388.             k++;
  389.         }
  390.     }
  391.     
  392.     // Get pointer to list
  393.     NXGetMemoryBuffer ( stream, &buf, &len, &mlen );
  394.     if ( buf ) files = NXCopyStringBuffer ( buf );
  395.     
  396.     // Close the stream
  397.     NXCloseMemory ( stream, NX_FREEBUFFER );
  398.     
  399.     // Now drag the files
  400.     if ( files ) {
  401.         
  402.         // Initiate a dragging sesssion
  403.         pt.x -= 24.0;    // Set pt to be center of icon
  404.         pt.y += 24.0;    
  405.         if ( k > 1 )
  406.             img = [NXImage findImageNamed:"multiple"]; // Multiple Items
  407.         else
  408.             img = [[self selectedCell] image]; // Single Item
  409.         pboard = [Pasteboard newName:NXDragPboard];
  410.         [pboard declareTypes:dragInTypes num:DRAGINTYPES owner:self];
  411.         [pboard writeType:NXFilenamePboardType data:files length:len];
  412.         [self dragImage:img at:&pt offset:&offs
  413.             event:theEvent pasteboard:pboard source:self slideBack:YES];
  414.         
  415.     }
  416.     
  417.     if ( files ) NX_FREE ( files );
  418.     return ( self );
  419. }
  420.  
  421.  
  422.  
  423. // -------------------------------------------------------------------------
  424. //   Making selections
  425. // -------------------------------------------------------------------------
  426.  
  427.  
  428. - selectAll:sender
  429. {
  430.     id ret = [super selectAll:sender];
  431.     [self determineSelectionCount];
  432.     return ( ret );
  433. }
  434.  
  435.  
  436. - selectCellAt:(int)row :(int)col
  437. {    
  438.     id ret = [super selectCellAt:row :col];
  439.     [self determineSelectionCount];
  440.     return ( ret );
  441. }
  442.  
  443.  
  444. - selectCell:aCell
  445. {
  446.     id ret = [super selectCell:aCell];
  447.     [self determineSelectionCount];
  448.     return ( ret );
  449. }
  450.  
  451.  
  452. - selectedCell
  453. /*
  454.     Returns the currently selected cell.  If there isn't one selected, or there is more than one, returns nil. Updates selectionCount to hold the number of cells selected
  455. */
  456. {
  457.     // Returns selectedCell only if a single cell is selected
  458.     if ( selectionCount == 1 ) return ( selectedCell );
  459.         else return ( nil );
  460. }
  461.  
  462.  
  463. - determineSelectionCount
  464. {
  465.     register int i, count;
  466.     
  467.     // Reset selectionCount
  468.     selectionCount = 0;
  469.     
  470.     // Get the number of selected cells
  471.     for ( i=0, count=[self cellCount]; i<count; i++ )
  472.         if ( [[self cellAt:i :0] state] ) selectionCount++;
  473.  
  474.     return ( self );
  475. }
  476.  
  477.  
  478. - selection
  479. /*
  480.     Acts like selectedCell, but if multiple items are selected, it returns the selection list ( see -selectionList)
  481. */
  482. {
  483.     static id list = nil;
  484.     
  485.     if ( list ) {
  486.         [list free];
  487.         list = nil;
  488.     }
  489.     if ( selectionCount == 1 ) return ( selectedCell );
  490.         else return ( [(list = [self selectionList]) count] ? list : nil );
  491. }
  492.  
  493.  
  494. - selectionList
  495. /*
  496.     Create an ItemList object and fill it with the items that are selected.  Caller must free.
  497. */
  498. {
  499.     id theList = [[ItemList alloc] initCount:selectionCount], item;
  500.     int i, count;
  501.     for ( i=0, count=[self cellCount]; i<count; i++ )
  502.         if ( [(item = [self cellAt:i :0]) state] ) [theList addObject:item];
  503.     return ( theList );
  504. }
  505.  
  506.  
  507. - (int)selectionCount
  508. {
  509.     return ( selectionCount );
  510. }
  511.  
  512.  
  513.  
  514. // -------------------------------------------------------------------------
  515. //  Drawing the matrix
  516. // -------------------------------------------------------------------------
  517.  
  518.  
  519. - drawSelf:(NXRect *)rects :(int)count
  520. /*
  521.     Draws a 'well' in place of the cell being dragged
  522. */
  523. {
  524.     int row, col;
  525.     NXRect cellBorder;
  526.     int sides[] = {    NX_XMIN, NX_YMIN, NX_XMAX, NX_YMAX,
  527.                 NX_XMIN, NX_YMIN};
  528.     float grays[] = {NX_DKGRAY, NX_DKGRAY, NX_WHITE,
  529.                 NX_WHITE, NX_BLACK, NX_BLACK};
  530.                
  531.     [super drawSelf:rects :count];
  532.     
  533.     // draw a "well" if the user's dragging a cell
  534.     if ( activeCell ) {
  535.         //get the cell's frame
  536.         [self getRow:&row andCol:&col ofCell:activeCell];
  537.         [self getCellFrame:&cellBorder at:row :col];
  538.       
  539.         // draw the well
  540.         if ( NXIntersectsRect ( &cellBorder, &(rects[0] ) ) ) {
  541.             NXDrawTiledRects ( &cellBorder, (NXRect *)0, sides, grays, 6 );
  542.             PSsetgray ( 0.17 );
  543.             NXRectFill ( &cellBorder );
  544.         }
  545.     }
  546.     
  547.     // Flush windowserver events to smooth scrolling
  548.     NXPing();
  549.  
  550.     return ( self );
  551. }
  552.  
  553.  
  554.  
  555. - scrollCellToVisible:(int)row :(int)col
  556. /*
  557.     Override default scrolling behavior to make it scroll the cell to the middle of the visible rectangle
  558. */
  559. {
  560.     NXRect visibleRect, cellFrame;
  561.     
  562.     [self getVisibleRect:&visibleRect];
  563.     [self getCellFrame:&cellFrame at:row :col];
  564.     
  565.     if ( !NXContainsRect ( &visibleRect, &cellFrame ) ) {
  566.         cellFrame.origin.y -= NX_HEIGHT(&visibleRect) / 2;
  567.         cellFrame.size.height += NX_HEIGHT(&visibleRect);
  568.     }
  569.     
  570.     return ( [self scrollRectToVisible:&cellFrame] );
  571. }
  572.  
  573.  
  574.  
  575. - sizeToCells
  576. {
  577.     NXRect newFrame;
  578.     
  579.     [super sizeToCells];
  580.     
  581.     // If in a ClipView, don't size any smaller than ClipView's width
  582.     if ( [superview isKindOf:[ClipView class]] ) {
  583.         [superview getDocVisibleRect:&newFrame];
  584.         newFrame.origin = frame.origin;
  585.         newFrame.size.height = frame.size.height;
  586.         newFrame.size.width = (newFrame.size.width > frame.size.width)
  587.             ? newFrame.size.width : frame.size.width;
  588.         [self setFrame:&newFrame];
  589.     }
  590.     
  591.     return ( self );
  592. }
  593.  
  594.  
  595.  
  596. // -------------------------------------------------------------------------
  597. //   Cache windows used to speed drawing
  598. // -------------------------------------------------------------------------
  599.  
  600.  
  601. - setupCacheWindows
  602. /*
  603.     Create and size the cache windows
  604. */
  605. {
  606.     NXRect visibleRect;
  607.     
  608.     // Create the matrix cache window
  609.     [self getVisibleRect:&visibleRect];
  610.     matrixCache = [self sizeCacheWindow:matrixCache to:&visibleRect.size];
  611.     
  612.     // Create the cell cache window
  613.     cellCache = [self sizeCacheWindow:cellCache to:&cellSize];
  614.     
  615.     return ( self );
  616. }
  617.  
  618.  
  619.  
  620. - sizeCacheWindow:cacheWindow to:(NXSize *)windowSize
  621. /*
  622.     Create ( if cahceWindow = nil ) and size the specified cache window
  623. */
  624. {
  625.     NXRect cacheFrame;
  626.     
  627.     if ( !cacheWindow ) {
  628.     
  629.         // Create the cache window if it doesn't exist
  630.         cacheFrame.origin.x = cacheFrame.origin.y = 0.0;
  631.         cacheFrame.size = *windowSize;
  632.         cacheWindow = [[[Window alloc] initContent:&cacheFrame
  633.             style:NX_PLAINSTYLE
  634.             backing:NX_RETAINED
  635.             buttonMask:0
  636.             defer:NO] reenableDisplay];
  637.         // Flip the contentView since we are flipped
  638.         [[cacheWindow contentView] setFlipped:YES];
  639.         
  640.     } else {
  641.     
  642.         // Make sure the cache window's the right size
  643.         [cacheWindow getFrame:&cacheFrame];
  644.         if ( NX_WIDTH(&cacheFrame) != windowSize->width || NX_HEIGHT(&cacheFrame) != windowSize->height )
  645.             [cacheWindow sizeWindow:windowSize->width :windowSize->height];
  646.             
  647.     }
  648.     
  649.     return ( cacheWindow );
  650. }
  651.  
  652.  
  653.  
  654. // -------------------------------------------------------------------------
  655. //   NXDraggingSource Protocol
  656. // -------------------------------------------------------------------------
  657.  
  658.  
  659. - (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
  660. {
  661.     return ( NX_DragOperationAll );
  662. }
  663.  
  664.  
  665.  
  666. @end
  667.  
  668.